use super::{
    err, err_value, json, ok, throw_err, Base, Client, Collect, ECode, KeyNode, Level, Lock, Resp,
    SvCache, VBase, VMap, Value,
};
use std::collections::HashSet;

pub struct Goods;

impl Base for Goods {}

impl Goods {
    pub async fn do_cmd(
        &self,
        model: &str,
        para: &Value,
        cmd: &str,
        client: &Client,
        cache: SvCache,
        lock: Lock,
    ) -> Resp {
        let data = para.ckobj()?;
        let line = data.ck_str("line")?;

        let collect = Collect::new(client, cache, line);

        match cmd {
            "rm" => {
                let sn = data.ck_str("k")?;
                let rmpsn = match data.k_bool("rmpsn") {
                    Some(v) => v,
                    None => true,
                };

                let k_all = collect.key_all(model)?;
                let k_goods = collect.key_sn(model, sn)?;
                let k_psn = collect.key_sub_sn(model, "psn", sn)?;

                let _ = lock.lock().await;

                let mut put = Vec::new();
                let mut del = vec![k_goods.to_string()];

                let help = Level::new(model, &collect);

                help.rm_year_item(&sn, &mut put, &mut del).await?;

                collect.del_mass_item(&k_all, sn, &mut put, &mut del);

                if rmpsn {
                    let _ = collect.del_mass(Some("psn"), &k_psn, &mut del).await?;
                }

                collect.commit(Some(&put), Some(&del)).await?;

                ok!()
            }
            "list" => {
                let help = Level::new(model, &collect);
                let body = help.list_year().await?;

                ok!(body)
            }
            "add" => {
                let val = data.ck_obj("v")?;
                let sn = val.ck_str_empty_trim("sn")?;
                let k_goods = collect.key_sn(model, &sn)?;

                let _ = lock.lock().await;

                let y = base::cur_time("%Y");
                let pstart = base::cur_time("%Y-%m-%d 00:00:00");

                let mut tmp = val.clone();
                tmp.insert("pstart".to_string(), json!(pstart));

                let mut put = vec![(k_goods, json!(tmp))];

                let help = Level::new(model, &collect);
                help.add_sn_to_year(&y, &sn, &mut put)?;

                collect.commit(Some(&put), None).await?;

                ok!()
            }
            "addpsn" => {
                let task_sn = data.ck_str_empty_trim("k")?;
                let val = data.ck_array("v")?;

                let k_task = collect.key_sub_sn(model, "psn", &task_sn)?;

                let _ = lock.lock().await;

                let mut psn_vec = Vec::new();

                for d in val.iter() {
                    let sn = d.ck_str("sn")?;
                    if sn.is_empty() {
                        continue;
                    }

                    psn_vec.push(json!(sn));

                    let k_psn = collect.key_sn("psn", sn)?;

                    if let Ok(_) = collect.item(&k_psn).await {
                        throw_err!(ECode::Psn, format!("psn repeat: {sn}"));
                    }
                }

                let mut put = Vec::new();
                collect.merge_mass_vec(&k_task, &psn_vec, &mut put)?;

                for d in val.iter() {
                    let mut cmd = d.ckobj()?.clone();

                    cmd.insert("task".to_string(), json!(task_sn));

                    let sn = cmd.ck_str("sn")?;
                    let k_psn = collect.key_sn("psn", sn)?;

                    put.push((k_psn, json!(cmd)));
                }

                collect.commit(Some(&put), None).await?;

                ok!()
            }
            "rmset" => {
                let _ = lock.lock().await;

                let task_sn = data.ck_str("k")?;
                let psn = data.ck_array("v")?;
                let k_task = collect.key_sub_sn(model, "psn", &task_sn)?;

                let rmpsn = match data.k_bool("rmpsn") {
                    Some(v) => v,
                    None => true,
                };

                let mut put = Vec::new();
                let mut del = Vec::new();
                let mut psn_set = HashSet::new();

                for d in psn.iter() {
                    let d = d.ckstr()?;
                    let k_psn = collect.key_sn("psn", d)?;

                    psn_set.insert(d.to_string());

                    if rmpsn {
                        del.push(k_psn);
                    }
                }

                collect.del_mass_set(&k_task, &psn_set, false, &mut put, &mut del);

                collect.commit(Some(&put), Some(&del)).await?;

                ok!()
            }
            "rmpsn" => {
                let _ = lock.lock().await;

                let psn = data.ck_str("k")?;
                let k_psn = collect.key_sn("psn", psn)?;

                let body = match collect.item(&k_psn).await {
                    Ok(v) => v,
                    Err(_) => {
                        return ok!();
                    }
                };

                let task_sn = body.ck_str("task")?;
                let k_task = collect.key_sub_sn(model, "psn", &task_sn)?;

                let mut put = Vec::new();
                let mut del = vec![k_psn.to_string()];

                let mut psn_set = HashSet::new();
                psn_set.insert(psn.to_string());

                collect.del_mass_item(&k_task, psn, &mut put, &mut del);

                collect.commit(Some(&put), Some(&del)).await?;

                ok!()
            }
            "movepsn" => {
                let _ = lock.lock().await;

                let sn = data.ck_str("k")?;
                let val = data.ck_array("v")?;
                let k_sn = collect.key_sub_sn(model, "psn", sn)?;

                let mut put = Vec::new();
                collect.merge_mass_vec(&k_sn, val, &mut put)?;

                collect.commit(Some(&put), None).await?;

                ok!()
            }
            "listpsn" => {
                let task_sn = data.ck_str("k")?;
                let k_sn = collect.key_sub_sn(model, "psn", task_sn)?;

                let ret = collect.list_array(&k_sn, "psn", None).await?;
                ok!(ret)
            }
            _ => collect.do_cmd(model, cmd, para, lock, None).await,
        }
    }
}
